﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Datamining.Models;

namespace Datamining.Program
{
    public class Pearson
    {
        #region Pearson berekeningen

        /// <summary>
        /// Berekent de Pearson waarde aan de hand van twee ingevoerde users. Returned 2 als een u1 en u2 hetzelfde object zijn.
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns>Geeft een double waarde terug wat de correlation tussen u1 en u2 aangeeft.</returns>
        private double PearsonCorrelation(UserPreferences u1, UserPreferences u2)
        {
            // Input 2 doubles
            if (u1 != u2)
            {
                // Geeft aantal items terug waarbij de waarde niet gelijk is aan 0.
                int[] items = u1.CommonItems(u2);
                int count = 0;
                for (int i = 0; i < items.Length; i++)
                {
                    if (items[i] != 0)
                    {
                        count++;
                    }
                }

                double sigU1U2 = SigmaOneUser(u1, u2, items);
                double sigU2U1 = SigmaOneUser(u2, u1, items);

                double top = (SigmaTwoUsers(u1, u2, items)) - ((sigU1U2 * sigU2U1 / count));
                double botSqrtOne = Math.Sqrt(SigmaOneUserPow(u1, u2, items) - ((Math.Pow(sigU1U2, 2)) / count));
                double botSqrtTwo = Math.Sqrt(SigmaOneUserPow(u2, u1, items) - ((Math.Pow(sigU2U1, 2)) / count));
                double botResult = (botSqrtOne * botSqrtTwo);

                return (top / botResult);
            }
            else
            {
                return -2;
            }
        }

        /// <summary>
        /// Geeft aanbevelingen welke producten zijn gekozen door de andere gebruikers die veel overeenkomsten hebben.
        /// Er kan een lege Dictionary worden gereturned als er geen aanbevelingen zijn.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns>Returned een Dictionary waarin de item aanbeveling voor de ingevoerde gebruiker zit. De value is een array van double waardes met daarin
        /// Index 0: userId;
        /// Index 1: itemId;
        /// Index 2: rating.
        /// Index 3: rating wegingsfactoor
        /// Index 4: Pearson correlation
        /// </returns>
        public Dictionary<int, Dictionary<int, double[]>> GetFutureRecommodation(int userId)
        {
            Dictionary<int, double> users = FindBestPersons(userId);
            Dictionary<int, Dictionary<int, double[]>> rec = new Dictionary<int, Dictionary<int, double[]>>();
            UserPreferences currentUser = Program.userRatingDictionary[userId];

            int[] array = currentUser.GetArticlesNotRatedByUser();
            int current = 0;

            foreach (KeyValuePair<int, double> item in users)
            {
                UserPreferences user = Program.userRatingDictionary[item.Key];
                int[] arr = user.getItemIdArray();

                Dictionary<int, double[]> userItems = new Dictionary<int, double[]>();

                for (int i = 0; i < arr.Length; i++)
                {
                    if (array.Contains(arr[i]))
                    {
                        double wegingsFactoor = user.getRatingByItemId(arr[i]) * item.Value;
                        userItems.Add(current, new double[] { user.UserId, arr[i], user.getRatingArray()[i], wegingsFactoor, item.Value });
                        current++;
                    }
                }

                rec.Add(user.UserId, userItems);
            }

            return rec;
        }

        public SortedDictionary<int, double> getArticleSimalarityByUserId(int userId)
        {
            Dictionary<int, Dictionary<int, double[]>> rec = GetFutureRecommodation(userId);
            Dictionary<int, double[]> usedItems = new Dictionary<int, double[]>();

            foreach (KeyValuePair<int, Dictionary<int, double[]>> currentUser in rec)
            {
                
                foreach (KeyValuePair<int, double[]> currentItem in currentUser.Value)
                {
                    int itemId =  (int) currentItem.Value[1];

                    if (!usedItems.ContainsKey(itemId))
                    {
                        // De item is nog niet in de lijst te vinden. Toevoegen.
                        usedItems.Add(itemId, new double[] { currentItem.Value[3] , currentItem.Value[4]});
                    }
                    else
                    {
                        // De item  bestaat al dus alleen de wegingsfactoor erbij optellen.
                        double[] currentItemValue = usedItems[itemId];
                        usedItems.Remove(itemId);
                        usedItems.Add(itemId, new double[] { currentItemValue[0] + currentItem.Value[3] , currentItem.Value[4] + currentItemValue[1]});
                    }
                }
            }

            SortedDictionary<int, double> result = new SortedDictionary<int, double>();

            foreach (KeyValuePair<int, double[]> item in usedItems)
            {
                double totaal = item.Value[0] / item.Value[1];
                result.Add(item.Key, totaal);
            }
            
            return result;
        }

        /// <summary>
        /// Zoekt de beste personen op die bij ingevoerde user past, beste 1% van de users wordt gereturned.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns>Een Dictionary object dat een lijst van personen bevat die volgens de PearsonCorrelation goed bij elkaar passen.</returns>
        private Dictionary<int, double> FindBestPersons(int userId)
        {
            Dictionary<int, double> users = new Dictionary<int, double>();

            int countUserRating = Program.userRatingDictionary.Count;

            for (int i = 1; i < countUserRating; i++)
            {
                double result = PearsonCorrelation(Program.userRatingDictionary[userId], Program.userRatingDictionary[i]);
                users.Add(Program.userRatingDictionary[i].UserId, result);
            }

            int aantalResults = (int)(countUserRating * 0.01);

            return (from entry in users orderby entry.Value descending select entry).Take(aantalResults)
                    .ToDictionary(pair => pair.Key, pair => pair.Value);
        }

        #endregion

        #region Sigma berekeningen

        /// <summary>
        /// Berekent de som van twee gebruikers. Roept de CommonItems op en voert de twee
        /// users hieraan zodat dit alleen wordt gedaan over de overeenkomstige producten.
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns>Rekent de sommatie tussen twee gebruikers uit. Voor elke sommatie worden de waardes tussen de gebruikers met elkaar vermenigvuldigt.</returns>
        private double SigmaTwoUsers(UserPreferences u1, UserPreferences u2, int[] items)
        {
            double result = 0.0;

            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != 0)
                {
                    double u1rating = u1.getRatingByItemId(items[i]);
                    double u2rating = u2.getRatingByItemId(items[i]);
                    result = result + (u1rating * u2rating);
                }
            }

            return result;
        }

        /// <summary>
        /// Berekent de som van 1 gebruiker. Roept de CommonItems op en voert de twee
        /// users hieraan zodat dit alleen wordt gedaan over de overeenkomstige producten.
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns>Rekent de sommatie van u1 uit. Hierbij wordt gebruiktgemaakt van de geemschappelijke items met u2. Hiervoor wordt de CommonItems() opgeroepen.</returns>
        private double SigmaOneUser(UserPreferences u1, UserPreferences u2, int[] items)
        {
            double result = 0.0;

            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != 0)
                {
                    double u1rating = u1.getRatingByItemId(items[i]);
                    result += u1rating;
                }
            }
            return result;
        }

        /// <summary>
        /// Berekent de som van 1 gebruiker tot de macht. Roept de CommonItems op en voert de twee
        /// users hieraan zodat dit alleen wordt gedaan over de overeenkomstige producten.
        /// </summary>
        /// <param name="u1"></param>
        /// <param name="u2"></param>
        /// <returns>Rekent de sommatie van u1 uit. Hierbij wordt gebruikt gemaakt van de gemeenschappelijke items met u2. Daarnaast wordt hierbij het resultaat tot de macht 2 gedaan.</returns>
        private double SigmaOneUserPow(UserPreferences u1, UserPreferences u2, int[] items)
        {
            double result = 0.0;

            for (int i = 0; i < items.Length; i++)
            {
                if (items[i] != 0)
                {
                    double u1rating = u1.getRatingByItemId(items[i]);
                    result += Math.Pow(u1rating, 2);
                }
            }
            return result;
        }

        #endregion

        #region Printen van data

        /// <summary>
        /// Print de future recommendations voor de ingevoerde gebruiker uit.
        /// </summary>
        /// <param name="userId"></param>
        public void PrintFutureRecommendation(int userId)
        {
            Console.WriteLine("");
            Console.WriteLine("Berekenen pearson + future recommendation.");
            Dictionary<int, Dictionary<int, double[]>> rec = GetFutureRecommodation(userId);
            Console.WriteLine("Klaar met berekenen pearson + future Recommendation.");

            Console.WriteLine("");
            Console.WriteLine("Begin printen van FutureRecommendations voor gebruiker id: " + userId + "\n");
            int count = rec.Count();

            if (count <= 0)
            {
                Console.WriteLine("Voor gebruiker: " + userId + " zijn geen aanbevelingen gevonden.");
            }
            else
            {
                int current = 0;
                foreach (KeyValuePair<int, Dictionary<int, double[]>> currentUser in rec)
                {
                    foreach (KeyValuePair<int, double[]> currentItem in currentUser.Value)
                    {
                        int itemId = (int)currentItem.Value[1];
                        double rating = currentItem.Value[2];
                        int gebruikerId = (int)currentItem.Value[0];

                        Console.WriteLine("Recommendation nummer: " + (current + 1) + " gekregen van gebruiker id: " + gebruikerId);
                        Console.WriteLine("Gebruiker id " + gebruikerId + " itemid: " + itemId);
                        Console.WriteLine("Gebruiker id " + gebruikerId + " Rating: " + rating);
                        Console.WriteLine("Film naam: " + Program.itemDictionary[itemId].itemName);
                        Console.WriteLine("--------------------------------------------");
                        Console.WriteLine("Druk op enter op de volgende uit te printen");
                        //Console.ReadLine();
                        current++;
                    }
                }
            }
        }

        /// <summary>
        /// Print voor de ingevoerde gebruiker alle artikelen uit die gerecommend worden met de wegingsfactoor waarde.
        /// </summary>
        /// <param name="userId"></param>
        public void PrintArticleSimalarityByUserId(int userId)
        {
            Console.WriteLine("");
            Console.WriteLine("Berekenen pearson + future recommendation + articlesimalarity.");
            SortedDictionary<int, double> dic = getArticleSimalarityByUserId(userId);
            Console.WriteLine("Klaar met berekenen pearson + future Recommendation + articlesimalarity.");

            foreach (KeyValuePair<int, double> item in dic)
            {
                int itemId = item.Key;
                double wegingsFactoor = item.Value;

                Console.WriteLine("Gebruiker id: " + userId + " heeft met film: ");
                Console.WriteLine("Voor itemid: " + itemId);
                Console.WriteLine("Filmnaam: " + Program.itemDictionary[itemId].itemName);
                Console.WriteLine("Heeft een simalarity van: " + wegingsFactoor + " \n");
                Console.WriteLine("--------------------------------------------");
                Console.WriteLine("Druk op enter op de volgende uit te printen");
                //Console.ReadLine();
            }
        }

        /// <summary>
        /// Print de best matchende personen uit voor de geselecteerde userid via het pearson algoritme.
        /// </summary>
        /// <param name="userId"></param>
        public void PrintBestPearsonMatchingPersons(int userId)
        {
            Console.WriteLine("Beginnen met het printen van de best matchende users via het Pearson algoritme voor user id: " + userId);

            Dictionary<int, double> users = FindBestPersons(userId);

            foreach (KeyValuePair<int, double> item in users)
            {
                Console.WriteLine("User ID: " + item.Key + " pearson result: " + item.Value);
            }
        }

        /// <summary>
        /// Print alleen de Pearson waarde uit van alle personen vergeleken met de userId
        /// </summary>
        /// <param name="userId"></param>
        public void PrintPearson(int userId)
        {
            Console.WriteLine("");
            for (int i = 1; i < Program.userRatingDictionary.Count; i++)
            {
                double result = PearsonCorrelation(Program.userRatingDictionary[userId], Program.userRatingDictionary[i]);

                if (result != -2)
                {
                    Console.WriteLine("Pearson nr: " + Program.userRatingDictionary[i].UserId + " similarity: " + result);
                    Console.WriteLine("Druk op enter op de volgende uit te printen");
                    Console.ReadLine();
                }
            }
        }

        #endregion
    }
}
